/** @file abstract_arguments.cc
 *
 *  @author Dongryeol Lee (drselee@gmail.com)
 */

#include "core/abstract/abstract_arguments.h"

namespace core {
namespace abstract {

AbstractArguments::AbstractArguments() {
  absolute_tolerance_ = 1e-2;
  max_num_iterations_ = 0;
  num_threads_ = 1;
  query_table_ = NULL;
  reference_table_ = NULL;
  relative_tolerance_ = 1e-4;

  // Initialize the description of the arguments.
  desc_ = new boost::program_options::options_description("Available options");
  desc_->add_options()(
    "help", "Print this information."
  )(
    "max_num_iterations_in",
    boost::program_options::value<int>()->default_value(50),
    "OPTIONAL The number of maximum sampling rounds."
  )(
    "model_out",
    boost::program_options::value<std::string>()->default_value(
      "trained_model.csv"),
    "The file to which the model will be outputted."
  )(
    "multi_queries_in",
    "If present, read from a set of multiple query files."
  )(
    "multi_references_in",
    "If present, read from a set of multiple references files."
  )(
    "num_threads_in",
    boost::program_options::value<int>()->default_value(4),
    "OPTIONAL The level of shared-memory parallelism."
  )(
    "predictions_out",
    boost::program_options::value<std::string>()->default_value(
      "predictions.csv"),
    "The file to which the predictions for the query will be outputted."
  )(
    "queries_in",
    boost::program_options::value<std::string>(),
    "OPTIONAL file containing query positions."
  )(
    "random_generate_n_attributes_in",
    boost::program_options::value<int>(),
    "OPTIONAL Generate the datasets on the fly of the specified dimension."
  )(
    "random_generate_num_points_in",
    boost::program_options::value<int>(),
    "OPTIONAL Generate the datasets on the fly of the specified number "
    "of points."
  )(
    "references_in",
    boost::program_options::value<std::string>(),
    "REQUIRED file containing reference data."
  );
}

void AbstractArguments::ValidateAbstract_() const {
  if(vm_["max_num_iterations_in"].as<int>() <= 0) {
    std::cerr << "The --max_num_iterations_in requires a positive integer.\n";
    exit(0);
  }
  if(vm_.count("random_generate_num_points_in") +
      vm_.count("random_generate_n_attributes_in") != 2 ||
      vm_.count("random_generate_num_points_in") +
      vm_.count("random_generate_n_attributes_in") == 0) {
    std::cerr <<
              "Needs --references_in or --random_generate_num_points_in and " <<
              "--random_generate_n_attributes_in in pairs...\n";
    exit(0);
  }

  if(vm_.count("random_generate_n_attributes_in") > 0) {
    if(vm_.count("random_generate_num_points_in") == 0) {
      std::cerr << "Missing required --random_generate_num_points_in.\n";
      exit(0);
    }
    if(vm_["random_generate_n_attributes_in"].as<int>() <= 0) {
      std::cerr << "The --random_generate_n_attributes_in requires a positive "
                "integer.\n";
      exit(0);
    }
  }
  if(vm_.count("random_generate_num_points_in") > 0) {
    if(vm_.count("random_generate_n_attributes_in") == 0) {
      std::cerr << "Missing required --random_generate_n_attributes_in.\n";
      exit(0);
    }
    if(vm_["random_generate_num_points_in"].as<int>() <= 0) {
      std::cerr << "The --random_generate_num_points_in requires a positive "
                "integer.\n";
      exit(0);
    }
  }
  if(vm_.count("random_generate_num_points_in") == 0 &&
      vm_.count("references_in") == 0) {
    std::cerr << "Missing required --references_in.\n";
    exit(0);;
  }
}

AbstractArguments::~AbstractArguments() {
  if(reference_table_ == query_table_) {
    delete reference_table_;
  }
  else {
    delete reference_table_;
    if(query_table_ != NULL) {
      delete query_table_;
    }
  }
  reference_table_ = NULL;
  query_table_ = NULL;

  delete desc_;
  desc_ = NULL;
}

bool AbstractArguments::Parse(
  boost::mpi::communicator &world, int argc, char *argv[]) {

  // Convert C input to C++; skip executable name for Boost.
  std::vector<std::string> args(argv + 1, argv + argc);

  boost::program_options::command_line_parser clp(args);
  clp.style(boost::program_options::command_line_style::default_style
            ^ boost::program_options::command_line_style::allow_guessing);
  try {
    boost::program_options::store(clp.options(* desc_).run(), vm_);
  }
  catch(const boost::program_options::invalid_option_value &e) {
    std::cerr << "Invalid Argument: " << e.what() << "\n";
    exit(0);
  }
  catch(const boost::program_options::invalid_command_line_syntax &e) {
    std::cerr << "Invalid command line syntax: " << e.what() << "\n";
    exit(0);
  }
  catch(const boost::program_options::unknown_option &e) {
    std::cerr << "Unknown option: " << e.what() << "\n";
    exit(0);
  }

  boost::program_options::notify(vm_);
  if(vm_.count("help")) {
    std::cout << *desc_ << "\n";
    return true;
  }

  // Validate the root parameters.
  ValidateAbstract_();

  // Parse the maximum number of iterations.
  max_num_iterations_ = vm_["max_num_iterations_in"].as<int>();

  // Parse the reference set.
  std::string reference_file_name =
    (vm_.count("references_in") > 0) ?
    vm_["references_in"].as<std::string>() : std::string("");
  reference_table_ = new core::data::MatrixDataset();
  if(vm_.count("random_generate_num_points_in") > 0) {
    reference_table_->RandomGenerate(
      world, vm_["random_generate_n_attributes_in"].as<int>(),
      vm_["random_generate_num_points_in"].as<int>());
  }
  else {
    std::cout << "Reading in the reference set: " <<
              reference_file_name << "\n";
    reference_table_->Init(
      world, reference_file_name, vm_.count("multi_references_in") == 0);
  }

  // Parse the query set.
  if(vm_.count("queries_in") > 0) {
    std::string query_file_name = vm_["queries_in"].as<std::string>();
    query_table_ = new core::data::MatrixDataset();
    if(vm_.count("random_generate_num_points_in") > 0) {
      std::stringstream query_file_name_sstr;
      query_file_name_sstr << vm_["queries_in"].as<std::string>() <<
                           world.rank();
      query_file_name = query_file_name_sstr.str();
      query_table_->RandomGenerate(
        world, vm_["random_generate_n_attributes_in"].as<int>(),
        vm_["random_generate_num_points_in"].as<int>());
    }
    else {
      std::cout << "Reading in the query set: " <<
                query_file_name << "\n";
      query_table_->Init(
        world, query_file_name, vm_.count("multi_queries_in") == 0);
    }
  }

  return this->Parse_(world, args);
}
}
}
